package org.sas.example.authentication.wechat;

import org.sas.example.constant.LoginTypeEnum;
import org.sas.example.constant.SecurityConstants;
import org.sas.example.constant.WeChatConstants;
import org.sas.example.constant.WorkWeChatConstants;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.function.Consumer;

/**
 * The enum Client providers.
 *
 * @author LiJY
 * @date 2024/07/11
 */
public enum ClientProviders {

    /**
     * 微信网页授权，需要携带appid和secret
     * scope为snsapi_base：
     * https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx520c15f417810387&redirect_uri=https%3A%2F%2Fchong.qq.com%2Fphp%2Findex.php%3Fd%3D%26c%3DwxAdapter%26m%3DmobileDeal%26showwxpaytitle%3D1%26vb2ctag%3D4_2030_5_1194_60&response_type=code&scope=snsapi_base&state=123#wechat_redirect
     * scope为snsapi_userinfo：
     * https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx807d86fb6b3d4fd2&redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
     */
    WECHAT_WEB_CLIENT(SecurityConstants.THIRD_LOGIN_WECHAT,
            builder ->
                    builder.attributes(attributes ->
                            builder.parameters(parameters -> {
                                //   client_id replace into appid here
                                LinkedHashMap<String, Object> linkedParameters = new LinkedHashMap<>();
                                //  根据微信官方提示：由于授权操作安全等级较高，所以在发起授权请求时，微信会对授权链接做正则强匹配校验，
                                //  如果链接的参数顺序不对，授权页面将无法正常访问
                                // 固定参数格式
                                if (parameters.containsKey(OAuth2ParameterNames.CLIENT_ID)) {
                                    linkedParameters.put(WeChatConstants.WECHAT_PARAMETER_APPID, parameters.get(OAuth2ParameterNames.CLIENT_ID));
                                    parameters.remove(OAuth2ParameterNames.CLIENT_ID);
                                }
                                if (parameters.containsKey(OAuth2ParameterNames.REDIRECT_URI)) {
                                    linkedParameters.put(OAuth2ParameterNames.REDIRECT_URI, parameters.get(OAuth2ParameterNames.REDIRECT_URI));
                                    parameters.remove(OAuth2ParameterNames.REDIRECT_URI);
                                }
                                linkedParameters.putAll(parameters);
                                parameters.clear();
                                parameters.putAll(linkedParameters);
                                builder.authorizationRequestUri(uriBuilder ->
                                        uriBuilder.fragment(WeChatConstants.WECHAT_REDIRECT)
                                                .build());
                            })),
            authorizationCodeGrantRequest -> {
                ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
                HttpHeaders headers = getTokenRequestHeaders(clientRegistration);
                OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();
                MultiValueMap<String, String> queryParameters = getParameters(authorizationCodeGrantRequest, authorizationExchange, clientRegistration);
                String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
                URI uri = UriComponentsBuilder.fromUriString(tokenUri).queryParams(queryParameters).build().toUri();
                return RequestEntity.get(uri).headers(headers).build();
            }),

    /**
     * 企业微信web 登录
     * 扫码登录页面：https://login.work.weixin.qq.com/wwlogin/sso/login?login_type=LOGIN_TYPE&appid=APPID&redirect_uri=REDIRECT_URI&state=STATE
     * https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=ID&corpsecret=SECRET
     */
    WORK_WECHAT_SCAN_CLIENT(SecurityConstants.THIRD_LOGIN_WORK_WECHAT,
            builder ->
                    builder.attributes(
                            attributes ->
                                    builder.parameters(parameters -> {
                                        LinkedHashMap<String, Object> linkedParameters = new LinkedHashMap<>();
                                        linkedParameters.put(SecurityConstants.OAUTH_LOGIN_TYPE, LoginTypeEnum.CorpApp.getValue());
                                        parameters.forEach((k, v) -> {
                                            if (OAuth2ParameterNames.CLIENT_ID.equals(k)) {
                                                linkedParameters.put(WorkWeChatConstants.APP_ID, v);
                                            }
                                            if (OAuth2ParameterNames.REDIRECT_URI.equals(k)) {
                                                linkedParameters.put(OAuth2ParameterNames.REDIRECT_URI, v);
                                            }
                                            if (OAuth2ParameterNames.STATE.equals(k)) {
                                                linkedParameters.put(OAuth2ParameterNames.STATE, v);
                                            }
                                        });
                                        linkedParameters.put(WorkWeChatConstants.AGENT_ID, WorkWeChatConstants.AGENT_ID_VALUE);
                                        parameters.clear();
                                        parameters.putAll(linkedParameters);
                                    })),
            authorizationCodeGrantRequest -> {
                String code = authorizationCodeGrantRequest.getAuthorizationExchange()
                        .getAuthorizationResponse()
                        .getCode();

                if (code == null) {
                    throw new OAuth2AuthenticationException("用户终止授权");
                }

                ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();

                MultiValueMap<String, String> queryParameters = new LinkedMultiValueMap<>();
                queryParameters.add(WorkWeChatConstants.CORP_ID, clientRegistration.getClientId());
                queryParameters.add(WorkWeChatConstants.CORP_SECRET, clientRegistration.getClientSecret());
                String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
                URI uri = UriComponentsBuilder.fromUriString(tokenUri)
                        .queryParams(queryParameters)
                        .build()
                        .toUri();
                return RequestEntity.get(uri).build();
            });

    /**
     * 微信授权登录，通过code换取网页授权access_token的请求 URL 参数设置
     *
     * @param authorizationCodeGrantRequest 授权码授予请求
     * @param authorizationExchange         授权交换
     * @param clientRegistration            客户注册
     * @return {@link MultiValueMap}<{@link String}, {@link String}>
     */
    private static MultiValueMap<String, String> getParameters(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest, OAuth2AuthorizationExchange authorizationExchange, ClientRegistration clientRegistration) {
        MultiValueMap<String, String> queryParameters = new LinkedMultiValueMap<>();
        //appid
        queryParameters.add(WeChatConstants.WECHAT_PARAMETER_APPID, clientRegistration.getClientId());
        //secret
        queryParameters.add(WeChatConstants.WECHAT_PARAMETER_SECRET, clientRegistration.getClientSecret());
        // code
        queryParameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
        // grant_type
        queryParameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
        // 如果有redirect-uri
        String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
        if (redirectUri != null) {
            queryParameters.add(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
        }
        return queryParameters;
    }

    private final String registrationId;
    private final Consumer<OAuth2AuthorizationRequest.Builder> oAuth2AuthorizationRequestConsumer;
    private final Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> tokenUriConverter;

    ClientProviders(String registrationId,
                    Consumer<OAuth2AuthorizationRequest.Builder> oAuth2AuthorizationRequestConsumer,
                    Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> tokenUriConverter) {
        this.registrationId = registrationId;
        this.oAuth2AuthorizationRequestConsumer = oAuth2AuthorizationRequestConsumer;
        this.tokenUriConverter = tokenUriConverter;
    }

    /**
     * Registration id string.
     *
     * @return the string
     */
    public String registrationId() {
        return registrationId;
    }

    /**
     * Request consumer consumer.
     *
     * @return the consumer
     */
    public Consumer<OAuth2AuthorizationRequest.Builder> requestConsumer() {
        return oAuth2AuthorizationRequestConsumer;
    }

    /**
     * Converter converter.
     *
     * @return the converter
     */
    public Converter<OAuth2AuthorizationCodeGrantRequest, RequestEntity<?>> converter() {
        return tokenUriConverter;
    }


    /**
     * Gets token request headers.
     *
     * @param clientRegistration the client registration
     * @return the token request headers
     */
    static HttpHeaders getTokenRequestHeaders(ClientRegistration clientRegistration) {
        HttpHeaders headers = new HttpHeaders();
        headers.setAccept(Collections.singletonList(MediaType.valueOf(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8")));
        final MediaType contentType = MediaType.valueOf(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
        headers.setContentType(contentType);
        if (ClientAuthenticationMethod.CLIENT_SECRET_BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
            String clientId = encodeClientCredential(clientRegistration.getClientId());
            String clientSecret = encodeClientCredential(clientRegistration.getClientSecret());
            headers.setBasicAuth(clientId, clientSecret);
        }
        return headers;
    }

    private static String encodeClientCredential(String clientCredential) {
        return URLEncoder.encode(clientCredential, StandardCharsets.UTF_8);
    }

}
